using System;
using System.Net;
using System.Text;

namespace HTTPSrv
{
    class HTTPOneUseConnection
    {
        System.Net.Sockets.TcpClient tCl;
        byte[] xBuffer = new byte[0];
        bool bBufferLocked = false;


        // event hooks...
        public event HTTPOnPathExistCheck OnPathExistCheck;
        public event HTTPOnRequest OnRequest;
        public event HTTPOnRequestWithData OnRequestWithData;

        bool PathExists(string Path)
        {
            if (OnPathExistCheck != null)
                // Yay! ^_^
                return OnPathExistCheck(Path);
            else
                // meh *shrugs*
                return true;
        }
        byte[] GetFile(string Path)
        {
            if (OnRequest != null)
                return OnRequest(Path);
            else
                return System.Text.Encoding.ASCII.GetBytes("Hook 'OnRequest' not connected, inside HTTPOneUseConnection...\r\n");
        }
        byte[] GetFileWithAddedData(string Path, HTTPSinglePair[] Datas)
        {
            if (OnRequestWithData != null)
                return OnRequestWithData(Path, Datas);
            else
                return System.Text.Encoding.ASCII.GetBytes("Hook 'OnRequestWithData' not connected, inside HTTPOneUseConnection...\r\n");
        }


        /// <summary>
        /// Gets whether or not the socket is connected to anything.
        /// </summary>
        public bool Active
        {
            get
            {
                // Poll();
                //System.Console.WriteLine((tCl != null).ToString());
                
               	return (tCl != null);
            }
        }

        /// <summary>
        /// Creates a new 'HTTPOneUseConnection', for wrapping a TcpClient nicely.
        /// </summary>
        /// <param name="tClient">The TcpClient (acquired from the TcpListener) to wrap and listen for data on..</param>
        public HTTPOneUseConnection(System.Net.Sockets.TcpClient tClient)
        {
            tCl = tClient;
            tCl.NoDelay = true;
            System.Console.WriteLine("NEw connection wrapped");

        }

        /// <summary>
        /// Shuts down the HTTPOneUseConnection, and closes the socket (if it's still live).
        /// </summary>
        ~HTTPOneUseConnection()
        {
            xBuffer = new byte[0];

            if (tCl != null)
            {
                tCl.Close();
                tCl = null;
            }
        }

        /// <summary>
        /// Requests a lock on the 'xBuffer' array, without any risk of other threads
        /// intefering... (if any)
        /// </summary>
        void LockBuffer()
        {
            // We're willing to wait 40 seconds for the buffer to unlock itself.
            // (Even a slow computer should be able to throw an 'Exception' in that time...)

            System.DateTime iEndWaitTime = System.DateTime.Now.AddSeconds(20);

            // First, wait for it to be unlocked...
            do
            {
                // Let any sleeping threads have some cycles...
                System.Threading.Thread.Sleep(0);
                
                // Locked out...?
                if (System.DateTime.Now.CompareTo(iEndWaitTime) > 0)
                    //.. waited too long -.-
                    throw new Exception("HTTPOneUseConnection appears to be locked!  Buffer has been locked by another thread for 20 seconds.  Check codez, reboot universe, and eat a liberal amount of paint stripper in the general hope of fixing this error.");

            } while (bBufferLocked);


            // Yayness.
            // Lock now. >:(
            bBufferLocked = true;
        }

        /// <summary>
        /// Unlocks the 'xBuffer' array.  Only call if you've previously locked it, _yourself_!!!
        /// </summary>
        void UnlockBuffer()
        {
            // Easy.
            bBufferLocked = false;
        }

        public void SendAndClose(byte[] Data)
        {
            tCl.GetStream().Write(Data, 0, Data.Length);
            tCl.GetStream().Close();
            tCl.Close();
        }

        public void Poll()
        {
            if (tCl != null)
            {
                // Ok. We have something to work with(!)   8-)
                //
                // Should we be putting this thing faster down than a sibling's white-encrusted bedsheets?
                if (tCl.Client == null || !tCl.Connected)
                {
                    tCl.Close();
                    tCl = null;
                    System.Console.WriteLine("Not connected, closing.");
                }
                else
                {
                    // Making thread-safe...
                    LockBuffer();

                    // Excellent. So we'll be...living, then ^^;
                    // Any new dataz?!1eone
                    if (tCl.Available > 0)
                    {
                        // I see..
                        int iDataToFetch = tCl.Available;
                        byte[] xTmpBuffer = new byte[xBuffer.Length + iDataToFetch];

                        tCl.GetStream().Read(xTmpBuffer, xBuffer.Length, iDataToFetch);

                        Array.ConstrainedCopy(xBuffer, 0, xTmpBuffer, 0, xBuffer.Length);

                        // And copy the temp buffer over the original. ^^
                        xBuffer = xTmpBuffer;

                        System.Console.WriteLine("Buffer now contains: '" + System.Text.Encoding.ASCII.GetString(xBuffer) + "'..");
                    }

                    // Kaydoneish..
                    //
                    // Wait!  Is the buffer ready for interpreting?! :o
                    // First, check that we at the very least have all of the headers.
                    string sTmpBuffer = System.Text.Encoding.ASCII.GetString(xBuffer);

                    if (sTmpBuffer.Length > 0 && sTmpBuffer.IndexOf("\r\n\r\n") > -1)
                    {
                        System.Console.WriteLine("On first glance, buffer seems ripe; processing...");
                        // ... YEP
                        // Put the first line of the headers through the GetHTTPFirstDetails thang...
                        HTTPFirstDetails hDetail = GetHTTPFirstDetails(sTmpBuffer.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries)[0]);

                        // If it's valid...
                        if (hDetail.Valid)
                        {
                            // Ok.  First, make sure that the HTTP version is acceptable...
                            if (hDetail.HTTPVersion == "HTTP/1.1" || hDetail.HTTPVersion == "HTTP/1.0")
                            {
                                // Yayness!1
                                // Now.  What wurdz do we have?
                                switch (hDetail.Method)
                                {
                                    case "HEAD":
                                        // Well, that's acceptable. ^_^
                                        // And shit..

                                        // decode url (if encoded <.<)
                                        hDetail.Data = HTTPUtil.URLDecode(hDetail.Data);

                                        // fire it off to the event already :p
                                        if (PathExists(hDetail.Data))
                                            // Awesome.
                                            SendAndClose(HTTPUtil.FormatResponse(GetFile(hDetail.Data), true));
                                        else
                                            SendAndClose(HTTPUtil.FormatResponse(HTTPUtil.HTTPStatusCodes.NotFound));

                                        System.Console.WriteLine("Serviced HEAD.");

                                        break;
                                    case "GET":
                                        // Also just dandy.
                                        // (decode url..)
                                        hDetail.Data = HTTPUtil.URLDecode(hDetail.Data);

                                        // Fire it off..
                                        if (PathExists(hDetail.Data))
                                            // yay!1
                                            SendAndClose(HTTPUtil.FormatResponse(GetFile(hDetail.Data), false));
                                        else
                                            SendAndClose(HTTPUtil.FormatResponse(HTTPUtil.HTTPStatusCodes.NotFound));

                                        System.Console.WriteLine("Serviced GET.");

                                        break;
                                    case "POST":
                                        // ... Ah.
                                        // Do we have the data we need?
                                        HTTPSinglePair Clh = FindContentLengthHeader(sTmpBuffer);

                                        // Is content-length just not there?! :(
                                        if (Clh.HeaderName != "Content-Length")
                                            SendAndClose(HTTPUtil.FormatResponse(HTTPUtil.HTTPStatusCodes.ContentLengthRequired));
                                        else
                                        {
                                            // Yayness!11eoen
                                            // So. IS it there? :o
                                            int iContentLength;

                                            try
                                            {
                                                iContentLength = int.Parse(Clh.HeaderValue);
                                            }
                                            catch //(Exception e)
                                            {
                                                // Pfft.
                                                SendAndClose(HTTPUtil.FormatResponse(HTTPUtil.HTTPStatusCodes.BadRequest));
                                                return;
                                            }

                                            // Ok..
                                            // Is it REALLY, REEEAAAALLLLYYYY THERE ?!!!!!!!!!?!?!?!?!??!!e/rdfbn
                                            // ;;
                                            if (xBuffer.Length >= iContentLength + sTmpBuffer.IndexOf("\r\n\r\n") + 1)
                                            {
                                                // Awesome.
                                                //... NOOW decode the url...
                                                hDetail.Data = HTTPUtil.URLDecode(hDetail.Data);

                                                // Soo, pass it on...
                                                // .. Does the file actually exist..?
                                                if (PathExists(hDetail.Data))
                                                {
                                                    // .. good!
                                                    SendAndClose(HTTPUtil.FormatResponse(OnRequestWithData(hDetail.Data, HTTPUtil.DecodeURLEncodedPOSTBody(sTmpBuffer.Substring(sTmpBuffer.IndexOf("\r\n\r\n") + 4, iContentLength))), false));
                                                }
                                                else
                                                {
                                                    // BAH! :'(
                                                    // Foiled again..
                                                    SendAndClose(HTTPUtil.FormatResponse(HTTPUtil.HTTPStatusCodes.NotFound));
                                                }
                                            }
                                            else
                                            {
                                                // Wait til later.
                                                UnlockBuffer();
                                                System.Console.WriteLine("Not ripe yet; Leaving. {[POST]}");
                                                return;
                                            }

                                        }
                                        System.Console.WriteLine("Serviced POST.");

                                        break;
                                    case "OPTIONS":
                                        // TODO! IMPLEMENT THIS :'(
                                        
                                        SendAndClose(HTTPUtil.FormatResponse(HTTPUtil.HTTPStatusCodes.NotImplemented));
                                        System.Console.WriteLine("Rejected OPTIONS [NOT IMPLEMENTED!].");

                                        break;
                                    case "PUT":
                                        // NOT SUPPORTED!
                                        SendAndClose(HTTPUtil.FormatResponse(HTTPUtil.HTTPStatusCodes.MethodNotAllowed));
                                        System.Console.WriteLine("Rejected PUT [WILL NEVER BE SUPPORTED].");
                                        break;
                                    case "DELETE":
                                        // NOT SUPPORTED!
                                        SendAndClose(HTTPUtil.FormatResponse(HTTPUtil.HTTPStatusCodes.MethodNotAllowed));
                                        System.Console.WriteLine("Rejected DELETE [WILL NEVER BE SUPPORTED].");
                                        break;
                                    case "TRACE":
                                        // NOT SUPPORTED!
                                        SendAndClose(HTTPUtil.FormatResponse(HTTPUtil.HTTPStatusCodes.MethodNotAllowed));
                                        System.Console.WriteLine("Rejected TRACE [WILL NEVER BE SUPPORTED].");
                                        break;
                                    case "CONNECT":
                                        // NOT A PROXY, FOO(!)
                                        SendAndClose(HTTPUtil.FormatResponse(HTTPUtil.HTTPStatusCodes.MethodNotAllowed));
                                        System.Console.WriteLine("Rejected CONNECT [NOT A PROXY YOU TRADER].");
                                        break;
                                    default:
                                        // ... yeah, whatever bitch.
                                        // NOT SUPPORTED!
                                        SendAndClose(HTTPUtil.FormatResponse(HTTPUtil.HTTPStatusCodes.BadRequest));
                                        System.Console.WriteLine("Didn't recognise request, rejecting...");
                                        break;
                                }
                            }
                            else
                            {
                                SendAndClose(HTTPUtil.FormatResponse(HTTPUtil.HTTPStatusCodes.HTTPVersionNotSupported));
                                System.Console.WriteLine("HTTP version not supported, rejecting...");
                            }
                        }
                        else
                        {
                            // Er, well shit.  It should've worked :/
                            SendAndClose(HTTPUtil.FormatResponse(HTTPUtil.HTTPStatusCodes.BadRequest));
                            System.Console.WriteLine("Badly formatted request; rejecting...");
                        }
                    }

                    // Keeping very thread-safe indeed...
                    UnlockBuffer();
                }
            }
        }


        HTTPFirstDetails GetHTTPFirstDetails(string Line)
        {
            HTTPFirstDetails hTmp = new HTTPFirstDetails();
            string[] sLineBits = Line.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

            hTmp.Valid = false;

            // Ok! How many bits of text; 1, 2 or Moar?
            switch (sLineBits.Length)
            {
                case 0:
                    // Not valid.
                    return hTmp;

                case 1:
                    // Only one wordthing, it's HTTP/0.9 or lesser.
                    // Not valid.
                    return hTmp;

                case 2:
                    // Two, it's some odd thing!
                    // Grab the method and the http version...
                    hTmp.Valid = true;
                    hTmp.Method = sLineBits[0].ToUpper();
                    hTmp.HTTPVersion = sLineBits[1].ToUpper();
                    hTmp.Data = "";
                    return hTmp;

                default:
                    // Ah.  Well, then.
                    string sTmp = "";

                    hTmp.Valid = true;
                    hTmp.Method = sLineBits[0].ToUpper();
                    hTmp.HTTPVersion = sLineBits[sLineBits.GetUpperBound(0)].ToUpper();

                    // Combine all the 'bits' to get a nice filename string....
                    for (int i = 1; i <= sLineBits.GetUpperBound(0) - 1; i++)
                        sTmp += sLineBits[i] + " ";

                    // Trim any excess spaces that may have clung on...
                    sTmp = sTmp.Trim();

                    // Aaand assign. ^.^
                    hTmp.Data = sTmp;

                    return hTmp;

            }
        }

        HTTPSinglePair FindContentLengthHeader(string Headers)
        {
            string[] HeaderBits = Headers.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);

            for (int i = 0; i <= HeaderBits.GetUpperBound(0); i++)
                if (HeaderBits[i].ToLower().Trim().StartsWith("content-length: "))
                {
                    HTTPSinglePair hsp = new HTTPSinglePair();

                    hsp.HeaderName = "Content-Length";
                    hsp.HeaderValue = HeaderBits[i].Replace('\r', ' ').Replace('\n', ' ').Trim().Substring("content-length: ".Length);

                    return hsp;
                }

            // Fuck. Well that didn't work :(
            return new HTTPSinglePair();
        }


    }

    public struct HTTPFirstDetails
    {
        public bool Valid;
        public string Method;
        public string HTTPVersion;
        public string Data;
    }

    /*
    public struct HTTPRequest
    {
        HTTPFirstDetails InitialDetail;
        HTTPSinglePair[] Headers;
        byte[] Body;
    }
    */

    public struct HTTPSinglePair
    {
        public string HeaderName;
        public string HeaderValue;
    }
}  
